#include <psp2common/types.h>
#include <psp2kern/kernel/sysclib.h>
#include <psp2kern/display.h>

#include "scr_print.h"
#include "font.h"
#include "font_cache.h"
#include "utils.h"

typedef struct ClipRect
{
	int sx;
	int sy;
	int dx;
	int dy;
} ClipRect;

#define TAB_SIZE 4u
#define WORD_SPACE 0u
#define LINE_SPACE 4u

static int s_srceen_width = 0;
static int s_srceen_height = 0;
static int s_srceen_pitch = 0;
static void *s_vram_base = NULL;

static ClipRect s_clip_rect = {0};
static uint8_t s_enable_clipping = 0;

static int s_tab_size = TAB_SIZE;
static int s_word_space = WORD_SPACE;
static int s_line_space = LINE_SPACE;

static uint32_t s_bg_col = 0xFF000000;
static uint32_t s_fg_col = 0xFFFFFFFF;

int utf8_to_ucs2(const char *utf8, unsigned int *character)
{
	if (((utf8[0] & 0xF0) == 0xE0) && ((utf8[1] & 0xC0) == 0x80) && ((utf8[2] & 0xC0) == 0x80))
	{
		*character = ((utf8[0] & 0x0F) << 12) | ((utf8[1] & 0x3F) << 6) | (utf8[2] & 0x3F);
		return 3;
	}
	else if (((utf8[0] & 0xE0) == 0xC0) && ((utf8[1] & 0xC0) == 0x80))
	{
		*character = ((utf8[0] & 0x1F) << 6) | (utf8[1] & 0x3F);
		return 2;
	}
	else
	{
		*character = utf8[0];
		return 1;
	}
}

int scrPrintInit()
{
	fontInit();
	return 0;
}

int scrPrintFinish()
{
	fontFinish();
	return 0;
}

void scrPtrintEnableClipping(int sx, int sy, int dx, int dy)
{
	// 越界检测
	if (sx >= dx || sy >= dy)
		return;
	if (sx < 0)
		sx = 0;
	if (sy < 0)
		sy = 0;
	if (dx > s_srceen_width)
		dx = s_srceen_width;
	if (dy > s_srceen_height)
		dy = s_srceen_height;

	s_clip_rect.sx = sx;
	s_clip_rect.sy = sy;
	s_clip_rect.dx = dx;
	s_clip_rect.dy = dy;

	s_enable_clipping = 1;
}

void scrPtrintDisableClipping()
{
	s_clip_rect.sx = 0;
	s_clip_rect.sy = 0;
	s_clip_rect.dx = s_srceen_width;
	s_clip_rect.dy = s_srceen_height;

	s_enable_clipping = 0;
}

void scrPrintSetFrameBuf(const SceDisplayFrameBuf *pParam)
{
	s_srceen_width = pParam->width;
	s_srceen_height = pParam->height;
	s_srceen_pitch = pParam->pitch;
	s_vram_base = pParam->base;

	if (!s_enable_clipping)
		scrPtrintDisableClipping();
}

void scrPrintSetTabSize(int size)
{
	s_tab_size = size;
}

void scrPrintSetWordSpace(int space)
{
	s_word_space = space;
}

void scrPrintSetLineSpace(int space)
{
	s_line_space = space;
}

void scrPrintSetBackColor(uint32_t color)
{
	s_bg_col = color;
}

void scrPrintSetTextColor(uint32_t color)
{
	s_fg_col = color;
}

int scrPrintGetScreenWidth()
{
	return s_srceen_width;
}

int scrPrintGetScreenHeight()
{
	return s_srceen_height;
}

int genericScrPrintfText(int draw, int *height, int x, int y, const char *text)
{
	unsigned int character;
	unsigned char *font, *font_ptr;
	int word_width, word_height;
	int word_width_bytes;
	uint32_t *vram, *vram_ptr;

	int font_size = fontGetFontSize();
	int start_x = x;
	int start_y = y;
	int min_sx = 0;
	int min_sy = 0;
	int pen_x = x;
	int pen_y = y;
	int pen_max_x = 0;
	int xx = 0;
	int yy = 0;
	int font_x = 0;
	int font_y = 0;
	int font_w = 0;
	int font_h = 0;

	uint32_t dacr;
	DACR_UNRESTRICT(dacr);

	int i;
	for (i = 0; text[i];)
	{
		i += utf8_to_ucs2(&text[i], &character);

		if (character == '\n')
		{
			if (pen_x > pen_max_x)
				pen_max_x = pen_x;
			pen_y += (font_size + s_line_space);
			pen_x = start_x;
			continue;
		}
		else if (character == '\t')
		{
			int j;
			for (j = 0; j < s_tab_size; j++)
			{
				pen_x += genericScrPrintfText(draw, NULL, pen_x, pen_y, " ");
			}
			continue;
		}

		font = fontGetData(character, &word_width, &word_height, &word_width_bytes);
		if (!font)
			continue;

		if (draw)
		{
			xx = pen_x;
			yy = pen_y + (font_size - word_height);
			font_x = 0;
			font_y = 0;
			font_w = word_width;
			font_h = word_height;

			// 越界检测
			min_sx = s_clip_rect.sx - word_width;

			if (xx < s_clip_rect.sx)
			{
				if (xx > min_sx)
				{
					font_x += (s_clip_rect.sx - xx);
					font_w -= (s_clip_rect.sx - xx);
					xx = s_clip_rect.sx;
				}
				else
				{
					goto NEXT;
				}
			}
			else if (xx >= s_clip_rect.dx)
			{
				goto NEXT;
			}

			min_sy = s_clip_rect.sy - word_height;

			if (yy < s_clip_rect.sy)
			{
				if (yy > min_sy)
				{
					font_y += (s_clip_rect.sy - yy);
					font_h -= (s_clip_rect.sy - yy);
					yy = s_clip_rect.sy;
				}
				else
				{
					goto NEXT;
				}
			}
			else if (yy >= s_clip_rect.dy)
			{
				goto NEXT;
			}

			if (font_w > s_clip_rect.dx - xx)
				font_w = s_clip_rect.dx - xx;

			if (font_h > s_clip_rect.dy - yy)
				font_h = s_clip_rect.dy - yy;

			vram = (uint32_t *)s_vram_base + xx + yy * s_srceen_pitch;
			font += (font_x / 8 + font_y * word_width_bytes);

			uint8_t is_fg_col;
			int sp = font_x % 8;

			int p;
			int row, col;
			for (row = 0; row < font_h; row++)
			{
				vram_ptr = vram;
				font_ptr = font;
				p = sp;
				for (col = 0; col < font_w; col++)
				{
					is_fg_col = *font_ptr & (0x80 >> p);
					if (is_fg_col)
						*vram_ptr = s_fg_col;
					else if (s_bg_col > 0xFFFFFF)
						*vram_ptr = s_bg_col;
					vram_ptr++;

					if (++p > 7)
					{
						font_ptr++;
						p = 0;
					}
				}
				vram += s_srceen_pitch;
				font += word_width_bytes;
			}
		}
	NEXT:
		pen_x += (word_width + s_word_space);
	}

	DACR_RESET(dacr);

	if (pen_x > pen_max_x)
		pen_max_x = pen_x;

	if (height)
		*height = pen_y + font_size - start_y;

	return (pen_max_x - start_x);
}

int scrPrintGetTextWidth(const char *text)
{
	return genericScrPrintfText(0, NULL, 0, 0, text);
}

int scrPrintGetTextHeight(const char *text)
{
	int height;
	genericScrPrintfText(0, &height, 0, 0, text);
	return height;
}

int scrPrintText(int x, int y, const char *text)
{
	return genericScrPrintfText(1, NULL, x, y, text);
}

int scrPrintfText(int x, int y, const char *format, ...)
{
	char buf[512];
	va_list opt;
	va_start(opt, format);
	vsnprintf(buf, sizeof(buf), format, opt);
	int res = scrPrintText(x, y, buf);
	va_end(opt);
	return res;
}

void scrPrintRectangle(int x, int y, int w, int h, uint32_t color)
{
	// 越界检测
	if (x >= s_clip_rect.dx || y >= s_clip_rect.dy)
		return;
	if (x < 0)
	{
		w += x;
		x = 0;
	}
	if (y < 0)
	{
		h += y;
		y = 0;
	}
	if (w <= 0 || h <= 0)
		return;
	if (w > s_clip_rect.dx - x)
		w = s_clip_rect.dx - x;
	if (h > s_clip_rect.dy - y)
		h = s_clip_rect.dy - y;

	uint32_t *vram = (uint32_t *)s_vram_base + x + y * s_srceen_pitch;
	uint32_t *vram_ptr;

	uint32_t dacr;
	DACR_UNRESTRICT(dacr);

	int row, col;
	for (row = 0; row < h; row++)
	{
		vram_ptr = vram;
		for (col = 0; col < w; col++)
		{
			*vram_ptr++ = color;
		}
		vram += s_srceen_pitch;
	}

	DACR_RESET(dacr);
}